package cn.tedu._07springaop.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 通过@Aspect标识该类为切面类
 * 切面类的本质为拦截
 * 通过@Component将该类交由Spring Framework管理
 */
@Aspect
@Component
public class LogAspect {
    @Before(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))")// 前置通知 执行目标方法之前先会执行该注解标识的内容
    // @Before 前置通知 在执行目标方法之前执行
    // execution() 切入点表达式
    // public int 权限标识符
    // cn.tedu._07springaop.aop.CalculatorImpl.*(..) 方法表达类型[具体路径]
    //  * 方法名随意 表示拦截所有路径下的指定方法
    // (..) 参数列表随意
    // 调用方法时会被拦截 先看有没有切面类 若有 进行拦截 结合切面类的逻辑完成后续操作
    public void before(JoinPoint joinPoint) {
        // JoinPoint joinPoint 连接点参数 目标方法的对象
        /**
         * 前置通知 目标方法执行之前执行
            1.获取目标方法名
            String name = joinPoint.getSignature().getName();
                                       获取签名     获取方法名
            2.获取目标方法参数
            String args = Arrays.toString(joinPoint.getArgs());
                                                     获取参数
            获取参数时 得到的是一个数组 为了简化代码 调用该数组的toString()方法
            直接输出参数数组
         */
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
//        System.out.println("xxx方法开始,参数为:xx和xx");
        System.out.println("[前置通知]:" + name + "方法开始,参数为:" + args);
    }

    /**
        后置通知
        1.后置通知是没有权限获取目标方法的返回值的 需要借助其他注解
        2.目标方法正常结束或异常结束(报错) 后置通知都会执行

        3.一般用于收尾工作 如释放资源 关闭连接等的操作

        * 后置通知 永远是最后执行
     */
    @After(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))")
    public void after(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println("[后置通知]:" + name + "方法执行结束.");
    }

    /**
     * @AfterReturning : 返回通知 目标方法正常结束后才会执行
     * 权限: 能够获取目标方法的返回值
     * <p>
     * 目标方法正常结束后 会将返回值给到此方法的result参数中
     * 若未规定具体方法 则result的类型为Object
     * <p>
     * 执行顺序为: 前置通知 -> 返回通知 -> 后置通知
     * <p>
     * 注意:AOP只能在目标方法执行前或执行后进行逻辑增强 在方法执行中不能穿插增强
     */
    @AfterReturning(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("[返回通知]:" + name + "方法正常执行,返回值为:" + result);
    }

    /**
     * 异常通知
     * 目标方法抛出异常时执行
     * ex:异常对象
     * <p>
     * 若目标方法抛出异常 返回通知不会执行 转而执行异常通知
     * 执行顺序为: 前置通知 -> 异常通知 -> 后置通知
     */
    @AfterThrowing(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        String name = joinPoint.getSignature().getName();
        String message = ex.getMessage();
        System.out.println("[异常通知]:" + name + "方法抛出异常,异常信息为:" + message);
    }

    /**
     * @Around 环绕通知:环绕目标方法执行
     *         等价于 @Before + @After + @AfterReturning + @AfterThrowing
     * 在方法中通过特定方法获取返回值或异常信息而非传参获取
     * 参数为 ProceedingJoinPoint 是 JoinPoint 的子类
     * ProceedingJoinPoint 能真正执行目标方法 将所有权限和流程都把控在自己手里 不需要 Spring Framework 自动执行目标方法
     *
     * 被 try{ 前置通知 执行目标方法 返回通知 }catch(异常对象){ 异常通知 }finally{ 后置通知 } 结构包裹
     * 执行流程:
     *  1.若未抛出异常: 前置通知 目标方法 返回通知 后置通知
     *  2.若抛出异常: 前置通知 目标方法 异常通知 后置通知
     */
    @Around(value = "execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))")
    public Object around (ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            // 前置通知
            System.out.println("环绕通知-前置通知");
            // 执行目标方法[专门方法以便执行目标方法]
            /*
                result: 等价于`返回通知`中的 returning
                作用: 获取返回值
             */
            result = joinPoint.proceed();
            // 返回通知
            System.out.println("环绕通知-返回通知");
        } catch (Throwable e){
            // 异常通知
            // e: 异常对象
            System.out.println("环绕通知-异常通知");
            throw new RuntimeException(e);
        } finally {
            // 后置通知
            System.out.println("环绕通知-后置通知");
        }
        return result;
    }
}
